package handle

import (
	"encoding/json"
	"fmt"
	"gitchina/CPTT4WeChat/data"
	"gitchina/CPTT4WeChat/errors"
	"gitchina/CPTT4WeChat/protobuf"
	"gitchina/CPTT4WeChat/util"
	"time"
)

type Stats struct {
	GardenId int
}

type CheckInStat struct {
	In     string `json:"in"`
	Abs    string `json:"abs"`
	Health string `json:"health"`
	Sick   string `json:"sick"`
	Watch  string `json:"watch"`
}

type CheckInOutData struct {
	State string `json:"state"`
	Time  string `json:"time"`
}

type CheckOutStat struct {
	In     string `json:"in"`
	Out    string `json:"out"`
	Stay   string `json:"stay"`
	OutPer string `json:"out_per"`
}

type OwnerData struct {
	OwnerId int64
	UserNo  string
	List    CheckInOutList
}

type StatsCacheData map[int64]map[int]StatList
type CheckInOutCacheData struct {
	LastUpdTime   string
	LastQueryTime string
	Data          map[string]OwnerData
}

var checkInStatListFn = func() StatList {
	l := NewCheckInStatList()
	return &l
}
var checkInStatItemFn = func() interface{} {
	return &CheckInStat{}
}
var checkOutStatListFn = func() StatList {
	l := NewCheckOutStatList()
	return &l
}
var checkOutStatItemFn = func() interface{} {
	return &CheckOutStat{}
}

var checkInOutDataCh = make(chan *protobuf.CheckInDataBatch)

func init() {
	for i := 0; i < 5; i++ {
		doAddOrUpdateCheckInData()
	}
}

func (s *Stats) AddOrUpdateCheckInStats(b *protobuf.CheckInStatsBatch) {
	var repo data.ApiRepo
	if err := repo.AddOrUpdateCheckInStats(b.GetBatch()); err != nil {
		util.GetLogger().Error("[stats-AddOrUpdateCheckInStats] - error:%s", err.Error())
	}
}

func (s *Stats) AddOrUpdateCheckInData(b *protobuf.CheckInDataBatch) {
	checkInOutDataCh <- b
}

func doAddOrUpdateCheckInData() {
	go func() {
		for {
			defer func() {
				if p := recover(); p != nil {
					util.GetLogger().Error("[handle-AddOrUpdateCheckInStats] - panic:%s", p)
				}
			}()
			b := <-checkInOutDataCh
			var repo data.ApiRepo
			if err := repo.AddOrUpdateCheckInData(b.GetBatch()); err != nil {
				util.GetLogger().Error("[stats-AddOrUpdateCheckInData] - error:%s", err.Error())
			}
		}
	}()
}

func (s *Stats) GetCheckInStatListGroupByGrade(openId string) (*CheckInStatList, error) {
	list, err := s.getStatListGroupByGrade(openId, 1, checkInStatListFn, checkInStatItemFn)
	return list.(*CheckInStatList), err
}

func (s *Stats) GetCheckOutStatListGroupByGrade(openId string) (*CheckOutStatList, error) {
	list, err := s.getStatListGroupByGrade(openId, 2, checkOutStatListFn, checkOutStatItemFn)
	return list.(*CheckOutStatList), err
}

func (s *Stats) getStatListGroupByGrade(openId string, dataType int, listFn func() StatList, itemFn func() interface{}) (StatList, error) {
	var err error
	var repo data.ApiRepo
	var usr User
	list := listFn()
	if cache, ok := util.GetCache(util.Grade_Stats_Cache); ok {
		if u, ok := usr.GetByOpenId(openId); ok {
			s.GardenId = int(u.GetGardenId())
			if u.GetUserType() == 1 { //admin
				if val, ok := cache.Get(fmt.Sprintf("%d%d", u.GetGardenId(), dataType)); ok {
					list = val.(StatList)
				} else {
					if checkInStats, err := repo.GetCheckInStatsGroupByGrade(int(u.GetGardenId()), dataType); err == nil {
						for _, dto := range checkInStats {
							stat := itemFn()
							if err := json.Unmarshal([]byte(dto.Data), stat); err == nil {
								list.Add(dto.CategoryName, dto.GroupId, stat)
							} else {
								util.GetLogger().Error("[stats-GetCheckInStatListGroupByGrade] - error:%s", err.Error())
							}
						}
						cache.Set(fmt.Sprintf("%d%d", u.GetGardenId(), dataType), list)
					} else {
						util.GetLogger().Error("[stats-GetCheckInStatListGroupByGrade] - error:%s", err.Error())
					}
				}
			} else {
				err = errors.New(errors.Query_Grade_Stats_List_Forbidden)
			}
		} else {
			util.GetLogger().Error("[stats-GetCheckInStatListGroupByGrade] - error: open_id:%s not bound.", openId)
		}
	}
	return list, err
}

func (s *Stats) GetCheckInOutOptionsGroupByGrade(openId string) (Options, error) {
	var err error
	var list *CheckInStatList
	opts := NewOptions()
	if list, err = s.GetCheckInStatListGroupByGrade(openId); err == nil {
		for _, val := range *list {
			if val.string != "全年级" {
				opts.Add(val.string, val.int)
			}
		}
	}
	return opts, err
}

func (s *Stats) GetCheckInStatListGroupByClass(openId string, groupId int) (*CheckInStatList, error) {
	var err error
	var list StatList
	if list, err = s.getStatsListGroupByClass(openId, groupId, 1, checkInStatListFn, checkInStatItemFn); err == nil {
		l := list.(*CheckInStatList)
		if len(*l) == 0 {
			err = errors.New(errors.Query_Class_Stats_List_Not_Exists)
		}
	}
	return list.(*CheckInStatList), err
}

func (s *Stats) GetCheckOutStatListGroupByClass(openId string, groupId int) (*CheckOutStatList, error) {
	var err error
	var list StatList
	if list, err = s.getStatsListGroupByClass(openId, groupId, 2, checkOutStatListFn, checkOutStatItemFn); err == nil {
		l := list.(*CheckOutStatList)
		if len(*l) == 0 {
			err = errors.New(errors.Query_Class_Stats_List_Not_Exists)
		}
	}
	return list.(*CheckOutStatList), err
}

func (s *Stats) getStatsListGroupByClass(openId string, groupId, dataType int, listFn func() StatList, itemFn func() interface{}) (StatList, error) {
	var err error
	var usr User
	var list StatList
	if cache, ok := util.GetCache(util.Class_Stats_Cache); ok {
		if u, ok := usr.GetByOpenId(openId); ok {
			s.GardenId = int(u.GetGardenId())
			var ownerId int64
			if u.GetUserType() == 2 {
				ownerId = u.GetId()
			}
			if val, ok := cache.Get(fmt.Sprintf("%d%d", u.GetGardenId(), dataType)); ok {
				cacheData := val.(StatsCacheData)
				if val, ok := cacheData[ownerId]; !ok {
					cacheData[ownerId] = make(map[int]StatList)
					s.fillCheckInStatsGroupByClass(s.GardenId, groupId, dataType, ownerId, cacheData, listFn, itemFn)
				} else {
					if _, ok := val[groupId]; !ok {
						s.fillCheckInStatsGroupByClass(s.GardenId, groupId, dataType, ownerId, cacheData, listFn, itemFn)
					}
				}
			} else {
				cacheData := make(map[int64]map[int]StatList)
				cacheData[ownerId] = make(map[int]StatList)
				s.fillCheckInStatsGroupByClass(s.GardenId, groupId, dataType, ownerId, cacheData, listFn, itemFn)
				cache.Set(fmt.Sprintf("%d%d", u.GetGardenId(), dataType), StatsCacheData(cacheData))
			}

			if val, ok := cache.Get(fmt.Sprintf("%d%d", u.GetGardenId(), dataType)); ok {
				cacheData := val.(StatsCacheData)
				if val, ok := cacheData[ownerId]; ok {
					list = val[groupId]
				} else {
					err = errors.New(errors.Query_Class_Stats_List_Forbidden)
				}
			}
		} else {
			util.GetLogger().Error("[stats-GetCheckInStatListGroupByClass] - error: open_id:%s not bound.", openId)
		}
	}

	return list, err
}

func (s *Stats) fillCheckInStatsGroupByClass(
	gardenId, groupId, dataType int, ownerId int64, cacheData map[int64]map[int]StatList, listFn func() StatList, itemFn func() interface{}) {
	var repo data.ApiRepo
	list := listFn()
	if checkInStats, err := repo.GetCheckInStatsGroupByClass(gardenId, groupId, dataType, ownerId); err == nil {
		for _, dto := range checkInStats {
			stat := itemFn()
			if err := json.Unmarshal([]byte(dto.Data), stat); err == nil {
				list.Add(dto.CategoryName, dto.GroupId, stat)
			} else {
				util.GetLogger().Error("[stats-GetCheckInStatListGroupByClass] - error:%s", err.Error())
			}
		}
		cacheData[ownerId][groupId] = list
	}
}

func (s *Stats) GetCheckInOutList(openId, keyword string, dataType int) (CheckInOutList, error) {
	var usr User
	var err error
	var list CheckInOutList
	if cache, ok := util.GetCache(util.Check_InOut_Data_Cache); ok {
		if u, ok := usr.GetByOpenId(openId); ok {
			s.GardenId = int(u.GetGardenId())
			if val, ok := cache.Get(fmt.Sprintf("%d%d", u.GetGardenId(), dataType)); !ok {
				cacheData := &CheckInOutCacheData{LastUpdTime: "2016-01-01 00:00:00", LastQueryTime: "2016-01-01 00:00:00", Data: make(map[string]OwnerData)}
				s.fillCheckInOutCacheData(int(u.GetGardenId()), dataType, cacheData, cache)
			} else {
				cacheData := val.(*CheckInOutCacheData)
				loc, _ := time.LoadLocation("Local")
				if t, _ := time.ParseInLocation("2006-01-02 15:04:05", cacheData.LastUpdTime, loc); t.Add(time.Minute).Before(time.Now()) {
					cacheData.LastUpdTime = t.Add(time.Minute).Format("2006-01-02 15:04:05")
					s.fillCheckInOutCacheData(int(u.GetGardenId()), dataType, cacheData, cache)
				}
			}
			if val, ok := cache.Get(fmt.Sprintf("%d%d", u.GetGardenId(), dataType)); ok {
				cacheData := val.(*CheckInOutCacheData)
				if ownerData, ok := cacheData.Data[keyword]; ok {
					if u.GetUserType() == 1 {
						list = ownerData.List
					} else if u.GetUserType() == 2 {
						if ownerData.OwnerId == u.GetId() {
							list = ownerData.List
						} else {
							err = errors.New(errors.Query_Check_In_Out_Forbidden1)
						}
					} else {
						if ownerData.UserNo == u.GetUserNo() {
							list = ownerData.List
						} else {
							err = errors.New(errors.Query_Check_In_Out_Forbidden2)
						}
					}
				} else {
					err = errors.New(errors.Query_Check_In_Out_Not_Exists)
				}
			}
		} else {
			util.GetLogger().Error("[stats-GetCheckInStatListGroupByClass] - error: open_id:%s not bound.", openId)
		}
	}

	return list, err
}

func (s *Stats) fillCheckInOutCacheData(gardenId, dataType int, cacheData *CheckInOutCacheData, c util.Cache) {
	var repo data.ApiRepo
	if checkInOutDataList, updTime, err := repo.GetCheckInData(
		gardenId, dataType, cacheData.LastQueryTime); err == nil {
		cacheData.LastUpdTime = updTime
		cacheData.LastQueryTime = updTime
		for _, val := range checkInOutDataList {
			var array []CheckInOutData
			ownerData := OwnerData{OwnerId: val.OwnerId, UserNo: val.UserNo, List: NewCheckInOutList(val.UserName)}
			if err := json.Unmarshal([]byte(val.Data), &array); err == nil {
				for _, val := range array {
					ownerData.List.AddData(val.State, val.Time)
				}
			}
			cacheData.Data[val.UserNo] = ownerData
			cacheData.Data[val.UserName] = ownerData
		}
		c.Set(fmt.Sprintf("%d%d", gardenId, dataType), cacheData)
	}
}
